Skip to content

feat(runtime): execute canonical agent specs in deterministic dry-run#6

Draft
ProfRandom92 wants to merge 1 commit into
mainfrom
p1/runtime-contract-integration
Draft

feat(runtime): execute canonical agent specs in deterministic dry-run#6
ProfRandom92 wants to merge 1 commit into
mainfrom
p1/runtime-contract-integration

Conversation

@ProfRandom92

Copy link
Copy Markdown
Owner

Objective

Implement executable dry-run and replay validation within the Rust runtime CLI.

Contract authority

The runtime depends on the canonical AIR schemas from comptext-air and implements native Rust types to mirror these structures.

Architecture

  • Added src/contracts.rs defining deserializable JSON types for AgentSpec, EvidenceEvent, CompletionContract, ReplayManifest, and ErrorEnvelope.
  • Added JCS canonicalization and SHA-256 hashing.
  • Added src/cli_p1.rs implementing CLI commands:
    • ctxt agent validate-spec <spec-path>
    • ctxt agent dry-run --spec <spec-path> --out-evidence <evidence-path> --out-replay <replay-path>
    • ctxt agent replay --replay <replay-path> --evidence <evidence-path>

Changes

  • Updated Cargo.toml and src/main.rs.
  • Added src/contracts.rs and src/cli_p1.rs.
  • Standardized error codes to return structured ErrorEnvelope format on failures.

Determinism guarantees

JCS sorted key serialization guarantees identical SHA-256 hashes on identical structures across platforms.

Capability security

Capability policy enforcement evaluates inputs and defaults to DENY for any unknown tools.

Evidence and replay

Monotonic sequence checks and back-linked SHA-256 parent hashes ensure evidence chain-of-custody.

Compatibility

Fully backward-compatible with existing 83 integration tests and 38 unit tests.

Tests

  • Added unit and integration tests inside src/cli_p1.rs testing dry-run events sequence, root hash determinism, and mutation rejections.
  • Added dynamic Schema Compatibility tests utilizing Python's jsonschema library from the Cargo test runner.

CI

Local tests are 100% green.

Known limitations

None.

Rollback

Git revert on merge commit.

Evidence

  • Command documentation: 02-p1-runtime-commands.md

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds agent-related commands (validate-spec, dry-run, and replay) to the CLI, introducing new modules for contract definitions and agent execution logic, alongside a security check to block access to sensitive directories. The feedback highlights several critical improvements: preventing a potential panic when replaying empty evidence logs, using case-insensitive comparisons to avoid security bypasses on case-insensitive file systems, buffering file writes to improve performance, and caching compiled regular expressions using OnceLock.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/cli_p1.rs
Comment on lines +417 to +424
if replay.evidence_root_hash != actual_events.last().unwrap().event_hash {
emit_p1_error(
"REPLAY_VERIFICATION_FAILED",
&format!("Root hash mismatch: manifest={}, final_event={}", replay.evidence_root_hash, actual_events.last().unwrap().event_hash),
None
);
return Ok(1);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the evidence log is empty (or both the replay manifest and evidence log are empty), calling actual_events.last().unwrap() will panic and crash the CLI instead of returning a structured error. We should safely handle the case where actual_events is empty by using last() with safe mapping or fallback.

Suggested change
if replay.evidence_root_hash != actual_events.last().unwrap().event_hash {
emit_p1_error(
"REPLAY_VERIFICATION_FAILED",
&format!("Root hash mismatch: manifest={}, final_event={}", replay.evidence_root_hash, actual_events.last().unwrap().event_hash),
None
);
return Ok(1);
}
let last_event_hash = actual_events.last().map(|e| e.event_hash.as_str()).unwrap_or("");
if replay.evidence_root_hash != last_event_hash {
emit_p1_error(
"REPLAY_VERIFICATION_FAILED",
&format!("Root hash mismatch: manifest={}, final_event={}", replay.evidence_root_hash, last_event_hash),
None
);
return Ok(1);
}

Comment thread src/cli.rs
for component in path.components() {
if let std::path::Component::Normal(os_str) = component {
if let Some(s) = os_str.to_str() {
if s == ".git" || s == ".ssh" || s == ".aws" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

On case-insensitive file systems (such as Windows or macOS), checking for exact matches like s == ".git" can be bypassed by using different casing (e.g., .GIT or .Ssh). Use eq_ignore_ascii_case to ensure the security policy cannot be bypassed via casing.

Suggested change
if s == ".git" || s == ".ssh" || s == ".aws" {
if s.eq_ignore_ascii_case(".git") || s.eq_ignore_ascii_case(".ssh") || s.eq_ignore_ascii_case(".aws") {

Comment thread src/cli_p1.rs
Comment on lines +301 to +310
fn write_events_to_file(events: &[EvidenceEvent], file_path: &str) -> Result<(), String> {
let mut file = File::create(file_path)
.map_err(|e| format!("Failed to create evidence file: {e}"))?;
for e in events {
let serialized = serde_json::to_string(e).unwrap();
writeln!(file, "{}", serialized)
.map_err(|e| format!("Failed to write to evidence file: {e}"))?;
}
Ok(())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Writing to a file line-by-line using writeln!(file, ...) without buffering performs a system call for every single line, which is highly inefficient. Wrapping the file in a BufWriter will buffer the writes and significantly improve performance.

fn write_events_to_file(events: &[EvidenceEvent], file_path: &str) -> Result<(), String> {
    let file = File::create(file_path)
        .map_err(|e| format!("Failed to create evidence file: {e}"))?;
    let mut writer = std::io::BufWriter::new(file);
    for e in events {
        let serialized = serde_json::to_string(e).unwrap();
        writeln!(writer, "{}", serialized)
            .map_err(|e| format!("Failed to write to evidence file: {e}"))?;
    }
    writer.flush().map_err(|e| format!("Failed to flush evidence file: {e}"))?;
    Ok(())
}

Comment thread src/cli_p1.rs
}

// Check regex: ^[a-z][a-z0-9_-]*$
let re = regex::Regex::new(r"^[a-z][a-z0-9_-]*$").unwrap_or_else(|_| regex::Regex::new(".*").unwrap());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Compiling the regular expression ^[a-z][a-z0-9_-]*$ on every invocation of handle_agent_validate_spec is inefficient. Since the pattern is static, we can compile it once using std::sync::OnceLock to reuse the compiled regex across calls.

    static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
    let re = RE.get_or_init(|| regex::Regex::new(r"^[a-z][a-z0-9_-]*$").unwrap());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant